/**

Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016.  All rights reserved.

Contact:
     SYSTAP, LLC DBA Blazegraph
     2501 Calvert ST NW #106
     Washington, DC 20008
     licenses@blazegraph.com

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
/*
 * Created on Oct 26, 2006
 */

package com.bigdata.util;

import org.apache.log4j.Logger;

/**
 * A timestamp factory based on {@link System#currentTimeMillis()}. Timestamps
 * reported by this factory are guarenteed to be distinct and strictly
 * increasing during the life cycle of the JVM. No guarentee is made if across
 * JVMs or system reboots. A means is available to inform the factory of the
 * earliest timestamp that it may serve. This may be used on restart to ensure
 * that time goes forward or when handing off from one timestamp service to
 * another.
 * <p>
 * Note: Time as reported by {@link System#currentTimeMillis()} can do crazy
 * things, including going backwards - presumably because of an error somewhere
 * in the time management stack (observed on Fedora core 6 with Sun JDK
 * 1.6.0_03). In these cases a warning is logged and the timestamp factory
 * begins to assign up one long integers instead. If time catches up, then
 * another warning is logged and the factory again begins to report timestamps
 * based on {@link System#currentTimeMillis()}. Note that when the factory is
 * using a one-up assignment it may appear to have a resolution finer than one
 * millisecond.
 * <p>
 * Note: method on this class are <code>synchronized</code> to ensure that
 * concurrent callers receive distinct timestamps. Likewise, the methods on this
 * class are <code>static</code> to ensure that assigned timestamps are global
 * for a JVM.
 * 
 * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
 * @version $Id$
 */
public class MillisecondTimestampFactory {

    private static final Logger log = Logger
            .getLogger(MillisecondTimestampFactory.class);

    /**
     * The initial lower bound for the assigned timestamps.
     */
    private static long _lastTimestamp = System.currentTimeMillis();

    /**
     * Initially the factory will use times as reported by
     * {@link System#currentTimeMillis()}.
     */
    private static boolean _autoIncMode = false;

    /**
     * Return <code>true</code> if the timestamp factory is in auto-increment
     * mode.
     */
    synchronized static public boolean isAutoIncMode() {

        return _autoIncMode;

    }

    /**
     * No public constructor.
     */
    private MillisecondTimestampFactory() {

    }

    /**
     * Sets the lower bound for the generated timestamps. This method may be
     * used to safely synchronize a failover timestamp service with a primary.
     * <strong>Extreme care should be used with this method as it can force the
     * factory to return timestamps out of order.</strong>
     * 
     * @param lowerBound
     *            The lower bound.
     * 
     * @throws IllegalArgumentException
     *             if the given timestamp is non-positive.
     */
    synchronized static public void setLowerBound(final long lowerBound) {

        assertPositive(lowerBound);

        if (_lastTimestamp < lowerBound) {

            log.warn("Timestamp factory is being set to an earlier time!");

        } else {

            log.info("Advancing: lowerBound=" + lowerBound);

        }

        _lastTimestamp = lowerBound;

        _autoIncMode = false;

    }

    /**
     * This is a paranoia check in case the timestamp overflows. The timestamp
     * value 0L and negative timestamps all have special interpretations for
     * bigdata so this factory MUST NOT assign a non-positive timestamp.
     * 
     * @param t
     *            A timestamp.
     * 
     * @throws IllegalStateException
     *             if <i>t</i> is non-positive.
     */
    private static void assertPositive(final long t) {

        if (t <= 0L) {

            throw new IllegalStateException("Timestamp is non-positive: " + t);

        }

    }

    /**
     * Generates a timestamp based on {@link System#currentTimeMillis()} that is
     * guaranteed to be distinct from the last timestamp generated by this
     * method during the life cycle of the JVM. No guarantee is made if across
     * JVMs or system reboots. However, a means is available to inform the
     * factory of the earliest timestamp that it may serve.
     * <p>
     * <p>
     * 
     * @return A timestamp with no more millisecond resolution.
     * 
     * @see System#currentTimeMillis()
     */
    synchronized static public long nextMillis() {

        //        final long lastTimestamp = lastTimestamp;

        // current time.
        long timestamp = System.currentTimeMillis();

        if (_autoIncMode) {

            if (timestamp < _lastTimestamp) {

                timestamp = _lastTimestamp + 1;

                // overflow and paranoia test.
                assertPositive(timestamp);

                // timestamp is Ok, so save it.
                _lastTimestamp = timestamp;

                // return the assigned timestamp value.
                return timestamp;

            }

            _autoIncMode = false;

            log
                    .warn("Leaving auto-increment mode: time is going forward again: lastTimestamp="
                            + _lastTimestamp + ", millisTime=" + timestamp);

            // fall through.

        }

        assert !_autoIncMode;

        //        // spin looking for a distinct timestamp.
        //        for(int i=0; i<1000 && timestamp == lastMillisTime; i++) {
        //
        //            timestamp = System.currentTimeMillis();
        //
        //        }

        // if not distinct, then sleep waiting for a distinct timestamp.
        while (timestamp == _lastTimestamp) {

            try {

                Thread.sleep(0, 1);

            } catch (InterruptedException ex) {

                // ignore.

            }

            timestamp = System.currentTimeMillis();

        }

        if (timestamp < _lastTimestamp) {

            /*
             * System.currentTimeMillis() is reporting a time that is less than
             * the last timestamp reported by this class. We switch over to an
             * auto-increment mode and recursively invoke ourselves to return a
             * one-up timestamp in order to keep the apparent time as reported
             * by this factory moving forward.
             */

            log
                    .warn("Entering auto-increment mode : milliseconds go backward: lastTimestamp="
                            + _lastTimestamp + ", millisTime=" + timestamp);

            _autoIncMode = true;

            return nextMillis();

        }

        // overflow and paranoia test.
        assertPositive(timestamp);

        // timestamp is Ok, so save it.
        _lastTimestamp = timestamp;

        // return the assigned timestamp value.
        return timestamp;

    }

}
